package fr.ul.ade.prime.service;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.IBinder;
import android.provider.BaseColumns;
import android.text.TextUtils;
import android.util.Log;
import fr.ul.ade.R;
import fr.ul.ade.prime.analyzer.ContainerData;
import fr.ul.ade.prime.analyzer.FeedAnalyzer;
import fr.ul.ade.prime.analyzer.FeedConnection;
import fr.ul.ade.prime.analyzer.FeedLesson;
import fr.ul.ade.prime.analyzer.FeedResourceEtud;
import fr.ul.ade.prime.config.Config;
import fr.ul.ade.prime.database.Database;
import fr.ul.ade.prime.exception.AAAException;
import fr.ul.ade.prime.exception.NoConnectionException;
import fr.ul.ade.prime.model.AdeWebOptions;
import fr.ul.ade.prime.model.GuidScheduleUpdater;
import fr.ul.ade.prime.model.IntentConstants;
import fr.ul.ade.prime.model.Lesson;
import fr.ul.ade.prime.model.LessonType;
import fr.ul.ade.prime.model.Project;
import fr.ul.ade.prime.model.Resource;
import fr.ul.ade.prime.model.Schedule;
import fr.ul.ade.prime.model.ScheduleState;
import fr.ul.ade.prime.model.ScheduleUpdater;
import fr.ul.ade.prime.provider.AdeWebContract;
import fr.ul.ade.prime.util.Lists;
import fr.ul.ade.prime.util.VersionUtils;

/**
 * @author Schnell Michaël
 * @version 1.0
 */
public class AdeWebService extends Service implements IntentConstants {

	private static final String SERVICE_PREFS_NAME = "fr.ul.ade.prime.Service"; //$NON-NLS-1$

	private static final String LAUNCH_AUTO_UPDATES = "fr.ul.ade.prime.LAUNCH_AUTO_UPDATE"; //$NON-NLS-1$

	protected static final String EMPTY = "";//$NON-NLS-1$

	protected static final long MILLIS_PER_HOUR = 60L * 60L * 1000L;

	protected static final long MILLIS_PER_DAY = 24L * MILLIS_PER_HOUR;

	/**
	 * Used for logging purposes.
	 */
	protected static final String TAG = AdeWebService.class.getSimpleName();

	// private static final long INITIAL_UPDATE_DELAY = 1000L ;

	private static final int DEPRECATED_LIMIT_IN_DAYS = 3;

	/**
	 * Queue containing the schedules waiting for an update.
	 */
	protected List<Long> updateQueue;

	/**
	 * Runnable used in a separate thread for updating the schedules in the
	 * update queue.
	 */
	protected ScheduledExecutorService scheduler;

	private final AdeWebServiceBinder binder = new AdeWebServiceBinder(this);

	/**
	 * {@inheritDoc}
	 */
	@Override
	public IBinder onBind(Intent intent) {
		if (AdeWebService.class.getName().equals(intent.getAction())) {
			return this.binder;
		}// if
		return null;
	}// onBind(Intent)

	@Override
	public void onCreate() {
		super.onCreate();
		this.updateQueue = Lists.newArrayList();
		this.scheduler = Executors.newScheduledThreadPool(5);

		if (AdeWebOptions.autoUpdateIsOn(this)) {
			this.startAutoUpdates();
		}// if
		this.startScheduleStateManager();
	}// onCreate()

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		if (intent != null) {
			String action = intent.getAction();
			if (action.equals(SCHEDULE_STATE_UPDATE_ACTION)) {
				this.updateDeprecatedScheduleStates();
			} else if (action.equals(SCHEDULE_AUTO_UPDATE_ACTION)) {
				this.launchAutoUpdate();
			} else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
				this.launcheUpdatesIfNecessary();
			}// if
		}// if
		return super.onStartCommand(intent, flags, startId);
	}// onStartCommand(Intent, int, int)
	
	/**
	 * For debug purposes only.
	 */
	public void clearUpdateQueue() {
		synchronized (this.updateQueue) {
			this.updateQueue.clear();
		}
	}

	protected void startAutoUpdates() {
		long nHours = AdeWebOptions.getnHoursBetweenUpdates(this);
		AlarmManager am = (AlarmManager) this.getSystemService(ALARM_SERVICE);
		Intent intent = new Intent(SCHEDULE_AUTO_UPDATE_ACTION);
		PendingIntent autoUpdateBroadcast = PendingIntent.getBroadcast(this,
				0,
				intent,
				0);
		long delay = nHours * MILLIS_PER_HOUR;
		am.setRepeating(AlarmManager.RTC,
				System.currentTimeMillis() + delay,
				delay,
				autoUpdateBroadcast);
	}// startAutoUpdates()

	protected void cancelAutoUpdates() {
		AlarmManager am = (AlarmManager) this.getSystemService(ALARM_SERVICE);
		Intent intent = new Intent(SCHEDULE_AUTO_UPDATE_ACTION);
		PendingIntent autoUpdateBroadcast = PendingIntent.getBroadcast(this,
				0,
				intent,
				0);
		am.cancel(autoUpdateBroadcast);
	}// cancelAutoUpdates()

	private void startScheduleStateManager() {
		// Start the "schedule status manager"
		AlarmManager am = (AlarmManager) this.getSystemService(ALARM_SERVICE);
		Intent intent = new Intent(SCHEDULE_STATE_UPDATE_ACTION);
		PendingIntent scheduleStateBroadCast = PendingIntent.getBroadcast(this,
				0,
				intent,
				0);
		long delay = MILLIS_PER_DAY;
		am.setRepeating(AlarmManager.RTC,
				System.currentTimeMillis() + delay,
				delay,
				scheduleStateBroadCast);
	}// startScheduleStateManager()

	public void createSchedule(String name, Resource[] resources, int period,
			Project project) throws AAAException {
		Schedule schedule = new Schedule(name, resources, period, project);
		Database db = new Database(this);
		schedule.validate(db);
		db.close();

		ContentValues values = new ContentValues();
		values.put(AdeWebContract.Schedule.SCHEDULE_NAME, schedule.getName());
		values.put(AdeWebContract.Schedule.SCHEDULE_RESOURCES,
				schedule.getResources());
		values.put(AdeWebContract.Schedule.SCHEDULE_STUDENT_NUM,
				schedule.getStudentNum());
		values.put(AdeWebContract.Schedule.SCHEDULE_PERIOD,
				Integer.valueOf(schedule.getPeriod()));
		values.put(AdeWebContract.Schedule.SCHEDULE_PROJECT_ID,
				Long.valueOf(project.getId()));
		values.put(AdeWebContract.Schedule.SCHEDULE_STATUS,
				Integer.valueOf(ScheduleState.OTHER.ordinal()));
		values.put(AdeWebContract.Schedule.SCHEDULE_CREATED,
				getFormattedCurrentDate());
		values.put(AdeWebContract.Schedule.SCHEDULE_NOTIFY_UPDATE,
				Integer.valueOf(Schedule.NOTIFY_TRUE));
		this.getContentResolver().insert(AdeWebContract.Schedule.CONTENT_URI,
				values);
	}

	public void editSchedule(Schedule newValues) throws AAAException {
		newValues.validateOnUpdate();

		ContentValues values = new ContentValues();
		values.put(AdeWebContract.Schedule.SCHEDULE_NAME, newValues.getName());
		values.put(AdeWebContract.Schedule.SCHEDULE_RESOURCES,
				newValues.getResources());
		values.put(AdeWebContract.Schedule.SCHEDULE_STUDENT_NUM,
				newValues.getStudentNum());
		values.put(AdeWebContract.Schedule.SCHEDULE_PERIOD,
				Integer.valueOf(newValues.getPeriod()));
		values.put(AdeWebContract.Schedule.SCHEDULE_PROJECT_ID,
				Long.valueOf(newValues.getProject().getId()));
		// values.put(AdeWebContract.Schedule.SCHEDULE_STATUS,
		// Integer.valueOf(newValues.getState().ordinal()));
		// values.put(AdeWebContract.Schedule.SCHEDULE_CREATED,
		// newValues.getCreated());
		// values.put(AdeWebContract.Schedule.SCHEDULE_LAST_UPDATE,
		// newValues.getLastUpdate());
		// values.put(AdeWebContract.Schedule.SCHEDULE_NOTIFY_UPDATE, Integer
		// .valueOf(newValues.isNotify() ? Schedule.NOTIFY_TRUE
		// : Schedule.NOTIFY_FALSE));
		this.getContentResolver()
				.update(ContentUris.withAppendedId(AdeWebContract.Schedule.CONTENT_URI,
						newValues.getId()),
						values,
						null,
						null);
	}

	public void removeSchedule(long scheduleId) /* throws AAAException */{
		Uri scheduleUri = ContentUris
				.withAppendedId(AdeWebContract.Schedule.CONTENT_URI, scheduleId);
		this.getContentResolver().delete(scheduleUri, null, null);
	}

	public void launchScheduleUpdate(long scheduleId) {
		this.launchScheduleUpdate(scheduleId, false, false);
	}// launchScheduleUpdate(int)

	private void launchScheduleUpdate(long scheduleId, boolean notify,
			boolean isBackground) {
		synchronized (this.updateQueue) {
			Long id = Long.valueOf(scheduleId);
			if (!this.updateQueue.contains(id)) {
				this.updateQueue.add(id);
				this.scheduler.execute(new ScheduleUpdateRunnable(scheduleId,
						notify, isBackground));
			}// if
		}// synchronized
	}// launchScheduleUpdate(int)

	public void setLessonType(long lessonId, LessonType newType) {
		Uri lessonUri = ContentUris
				.withAppendedId(AdeWebContract.Lesson.CONTENT_URI, lessonId);
		ContentValues values = new ContentValues();
		values.put(AdeWebContract.Lesson.LESSON_TYPE,
				Integer.valueOf(newType.ordinal()));
		String selection = null;
		String[] selectionArgs = null;

		this.getContentResolver().update(lessonUri,
				values,
				selection,
				selectionArgs);
	}

	public void removeLessonHistory(long lessonId) {
		Uri lessonUri = ContentUris
				.withAppendedId(AdeWebContract.Lesson.CONTENT_URI, lessonId);
		ContentValues values = new ContentValues();
		values.put(AdeWebContract.Lesson.LESSON_CHANGE,
				Integer.valueOf(Lesson.NO_CHANGE));
		this.getContentResolver().update(lessonUri, values, null, null);
	}

	private static String getFormattedCurrentDate() {
		return Database.dateFormatter.format(new Date());
	}
	
	protected boolean isLastSchedule() {
		boolean isLast = false;
		synchronized (this.updateQueue) {
			isLast = this.updateQueue.size() == 1;
		}
		return isLast;
	}

	protected class ScheduleUpdateRunnable implements Runnable {

		protected final Uri scheduleUri;

		protected long scheduleId;

		protected String resources;

		protected String studentNumbers;

		protected int period;

		protected int adeProjectId;

		protected FeedConnection connection;

		protected List<FeedLesson> lessonsFeed;

		protected Schedule newSchedule;

		private ScheduleUpdater scheduleDiff;

		private ScheduleState previousState;

		private boolean isBackground;

		private boolean notify;

		private String lastUpdate;

		public ScheduleUpdateRunnable(long scheduleId, boolean notify,
				boolean isBackground) {
			this.scheduleId = scheduleId;
			this.scheduleUri = ContentUris
					.withAppendedId(AdeWebContract.Schedule.CONTENT_URI,
							this.scheduleId);
			this.notify = notify;
			this.isBackground = isBackground;
			this.previousState = null;
		}// ScheduleUpdateRunnable(long)

		@Override
		public void run() {
			try {
				if (!VersionUtils.hasGingerBread()) {
					System.setProperty("http.keepAlive", "false");
				}// if
					// Log.d(TAG, ">>>>> Start update for id: " +
					// this.scheduleId);
				this.retrieveSchedule();
				this.setScheduleState(ScheduleState.WAITING_FOR_UPDATE);
				// Log.d(TAG, ">>>>> Connecting to api for id: " +
				// this.scheduleId);
				this.connectToAdeApi();
				// Log.d(TAG, ">>>>> Setting project for id: " +
				// this.scheduleId);
				this.setProject();
				// Log.d(TAG, ">>>>> Retrieving student resources for id: "
				// + this.scheduleId);
				// Log.d(TAG, "      Before for id " + this.scheduleId + ": "
				// + this.resources);
				this.retrieveStudentNumbers();
				// Log.d(TAG, "      Retrieved resources for id "
				// + this.scheduleId + ": " + this.resources);
				// Log.d(TAG, ">>>>> Getting lessons for id: " +
				// this.scheduleId);
				this.getLessonsFeed();
				// Log.d(TAG, ">>>>> Analyzing lesson for id: " +
				// this.scheduleId);
				this.analyzeLessonsFeed();
				// Log.d(TAG, ">>>>> Comparing for id: " + this.scheduleId);
				this.compareWithPreviousSchedule();
				// Log.d(TAG, ">>>>> Updating for id: " + this.scheduleId);
				this.updateScheduleInsideDatabase();
				// Log.d(TAG, ">>>>> End update for d: " + this.scheduleId);

				if (this.notify || AdeWebService.this.isLastSchedule()) {
					AdeWebNotificationManager
							.notifyImportantScheduleChange(AdeWebService.this,
									this.scheduleId,
									this.scheduleDiff);
				}// if
				ContentValues values = new ContentValues();
				values.put(AdeWebContract.Schedule.SCHEDULE_STATUS,
						Integer.valueOf(ScheduleState.UPDATED.ordinal()));
				values.put(AdeWebContract.Schedule.SCHEDULE_LAST_UPDATE,
						String.format("%1$tY/%1$tm/%1$td %1$tR", Calendar.getInstance())); //$NON-NLS-1$
				ContentResolver resolver = AdeWebService.this
						.getContentResolver();
				resolver.update(this.scheduleUri, values, null, null);
				resolver.notifyChange(AdeWebContract.Lesson
						.buildScheduleUri(this.scheduleId), null, false);

				SharedPreferences preferences = AdeWebService.this
						.getSharedPreferences(AdeWebService.SERVICE_PREFS_NAME,
								MODE_PRIVATE);
				if (preferences.contains(AdeWebService.LAUNCH_AUTO_UPDATES)) {
					preferences.edit()
							.remove(AdeWebService.LAUNCH_AUTO_UPDATES).commit();
					AdeWebService.this.startAutoUpdates();
				}// if
			} catch (Exception e) {
				if (this.isBackground && (this.previousState != null)) {
					Calendar calendar = Calendar.getInstance();
					int nowDayYear = calendar.get(Calendar.DAY_OF_YEAR);
					try {
						// TODO Externalize date format string
						calendar.setTime(new SimpleDateFormat(
								"yyyy/MM/dd HH:mm").parse(this.lastUpdate)); //$NON-NLS-1$
					} catch (ParseException pe) {
						/*
						 * Inutile de traiter l'erreur, l'emploi du temps garde
						 * son état initial.
						 */
					}// try
					int dayYear = calendar.get(Calendar.DAY_OF_YEAR);
					if (dayYear < nowDayYear) {
						dayYear += 365;
					}// if
					if (dayYear - nowDayYear <= Config.DECREPATED_DELAY) {
						this.setScheduleState(this.previousState);
					} else {
						this.setScheduleState(ScheduleState.OTHER);
						AdeWebNotificationManager
								.notifyScheduleUpdateFailed(AdeWebService.this);
					}// if
				} else {
					this.setScheduleState(ScheduleState.NOT_UPDATED);
				}// if

				// TODO Handle exception - make it recoverable for the
				// activities
				Log.e(TAG, "Update failed: " + e.getMessage(), e); //$NON-NLS-1$
			}// try
			synchronized (AdeWebService.this.updateQueue) {
				AdeWebService.this.updateQueue.remove(Long
						.valueOf(this.scheduleId));
			}// synchronized
		}

		protected void retrieveSchedule() throws AAAException {
			final Cursor cursor = AdeWebService.this.getContentResolver()
					.query(this.scheduleUri,
							ScheduleQuery.PROJECTION,
							null,
							null,
							AdeWebContract.Schedule.DEFAULT_SORT);
			if (cursor.moveToFirst()) {
				this.studentNumbers = cursor
						.getString(ScheduleQuery.SCHEDULE_STUDENT_NUM);
				this.resources = cursor
						.getString(ScheduleQuery.SCHEDULE_RESOURCES);
				this.period = cursor.getInt(ScheduleQuery.SCHEDULE_PERIOD);
				this.adeProjectId = cursor
						.getInt(ScheduleQuery.SCHEDULE_ADE_PROJECT_ID);
				this.previousState = ScheduleState.getState(cursor
						.getInt(ScheduleQuery.SCHEDULE_STATE));
				this.lastUpdate = cursor
						.getString(ScheduleQuery.SCHEDULE_LAST_UPDATE);
			} else {
				throw new AAAException(
						String.format("EDT inexistant: uri: %s", this.scheduleUri)); //$NON-NLS-1$
			}// if
			cursor.close();
		}// retrieveSchedule()

		protected void connectToAdeApi() throws AAAException {
			this.setScheduleState(ScheduleState.UPDATING_STEP_1);

			// Check network availability
			this.checkNetworkAvailability();

			this.connection = ContainerData.getFeedConnexion();
			if (this.connection == null) {
				String errmsg = AdeWebService.this.getResources()
						.getString(R.string.ADE_connection_failed);
				throw new AAAException(errmsg);
			}// if
		}// connectToAdeApi(Schedule)

		protected void retrieveStudentNumbers() throws AAAException {
			if (!TextUtils.isEmpty(this.studentNumbers)) {
				List<String> studenResources = Lists.newArrayList();
				for (String sn : this.studentNumbers
						.split(Config.ESCAPED_RESOURCE_SEPARATOR)) {
					// Récupération des ressources associées à un numéro
					// étudiant
					ArrayList<FeedResourceEtud> resNum = ContainerData
							.getFeedsResources(this.connection.getId(), sn);
					for (FeedResourceEtud feed : resNum) {
						studenResources.add(feed.getId());
					}// for
				}// for
				this.resources = this.resources
						+ Config.RESOURCE_SEPARATOR
						+ TextUtils.join(Config.RESOURCE_SEPARATOR,
								studenResources);
				if (this.resources.startsWith(Config.RESOURCE_SEPARATOR)) {
					this.resources = this.resources
							.substring(Config.RESOURCE_SEPARATOR.length());
				}// if
			}// if
		}// retrieveStudentNumbers()

		private void checkNetworkAvailability() throws NoConnectionException {
			ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
			NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
			if ((networkInfo == null) || !networkInfo.isConnected()) {
				throw new NoConnectionException();
			}// if
		}// checkNetWorkAvailability()

		protected void setProject() throws AAAException {
			this.setScheduleState(ScheduleState.UPDATING_STEP_2);

			String sessionId = this.connection.getId();
			ContainerData.getFeedSetProject(sessionId, this.adeProjectId);
		}// setProject(Schedule)

		protected void getLessonsFeed() throws AAAException {
			this.setScheduleState(ScheduleState.UPDATING_STEP_3);

			String sessionId = this.connection.getId();
			this.lessonsFeed = ContainerData.getFeedsLesson(sessionId,
					this.resources,
					this.period);
			// On a fini la récupération de données depuis ADE, on se déconnecte
			this.connection = ContainerData.getFeedDeconnexion(sessionId);
		}// getLessonsFeed()

		protected void analyzeLessonsFeed() {
			this.setScheduleState(ScheduleState.UPDATING_STEP_4);

			this.newSchedule = new Schedule();
			this.newSchedule.setId(this.scheduleId);
			FeedAnalyzer feedAnalyzer = new FeedAnalyzer(this.newSchedule,
					this.lessonsFeed);
			feedAnalyzer.start();
		}// analyzeLessonsFeed()

		protected void compareWithPreviousSchedule() {
			this.setScheduleState(ScheduleState.UPDATING_STEP_5);

			Uri lessonsUri = AdeWebContract.Lesson
					.buildScheduleUri(this.scheduleId);
			final Cursor lessons = AdeWebService.this.getContentResolver()
					.query(lessonsUri,
							LessonQuery.PROJECTION,
							LessonQuery.SELECTION,
							null,
							AdeWebContract.Lesson.DEFAULT_SORT);
			Schedule schedule = new Schedule();
			schedule.setId(this.scheduleId);
			schedule.ensureCapacity(lessons.getCount());
			while (lessons.moveToNext()) {
				Lesson lesson = new Lesson();
				lesson.setLessonId(lessons.getLong(LessonQuery._ID));
				lesson.setTime(lessons.getString(LessonQuery.LESSON_DATE),
						lessons.getString(LessonQuery.LESSON_START_TIME),
						lessons.getString(LessonQuery.LESSON_END_TIME));
				lesson.setRoom(lessons.getString(LessonQuery.LESSON_ROOMS));
				lesson.setTeachers(lessons
						.getString(LessonQuery.LESSON_TEACHERS));
				lesson.setType(LessonType.values()[lessons
						.getInt(LessonQuery.LESSON_TYPE)]);
				lesson.setGroups(lessons.getString(LessonQuery.LESSON_GROUPS));
				lesson.setGuid(lessons.getString(LessonQuery.LESSON_GUID));
				lesson.setTitle(lessons.getString(LessonQuery.LESSON_TITLE));
				schedule.append(lesson);
			}// for
			lessons.close();
			this.scheduleDiff = new GuidScheduleUpdater();
			this.scheduleDiff.compare(schedule, this.newSchedule);
		}// compareWithPreviousSchedule()

		protected void updateScheduleInsideDatabase() throws AAAException {
			this.setScheduleState(ScheduleState.UPDATING_STEP_6);

			this.scheduleDiff.updateSchedule(AdeWebService.this);
		}// updateScheduleInsideDatabase()

		protected void setScheduleState(ScheduleState state) {
			ContentValues values = new ContentValues();
			values.put(AdeWebContract.Schedule.SCHEDULE_STATUS,
					Integer.valueOf(state.ordinal()));
			AdeWebService.this.getContentResolver().update(this.scheduleUri,
					values,
					null,
					null);
		}// setScheduleState(Schedule, ScheduleState)

	}// class ScheduleUpdateRunnable

	/**
	 * 
	 */
	private void updateDeprecatedScheduleStates() {
		final Cursor cursor = this.getContentResolver()
				.query(AdeWebContract.Schedule.CONTENT_URI,
						DeprecatedScheduleQuery.PROJECTION,
						null,
						null,
						AdeWebContract.Schedule.DEFAULT_SORT);

		String limit = getDeprecatedLimit();
		List<Long> oldScheduleIds = Lists.newArrayList();
		while (cursor.moveToNext()) {
			String lastUpdate = cursor
					.getString(DeprecatedScheduleQuery.SCHEDULE_LAST_UPDATE);
			if ((lastUpdate != null) && (limit.compareTo(lastUpdate) >= 0)) {
				oldScheduleIds.add(Long.valueOf(cursor
						.getLong(DeprecatedScheduleQuery._ID)));
			}// if
		}// while
		cursor.close();

		if (!oldScheduleIds.isEmpty()) {
			Uri scheduleUri = AdeWebContract.Schedule.CONTENT_URI;
			ContentValues values = new ContentValues();
			values.put(AdeWebContract.Schedule.SCHEDULE_STATUS,
					Integer.valueOf(ScheduleState.OTHER.ordinal()));
			String where = String.format("%s IN (%s)", //$NON-NLS-1$
					BaseColumns._ID,
					TextUtils.join(",", oldScheduleIds)); //$NON-NLS-1$
			this.getContentResolver().update(scheduleUri, values, where, null);
		}// if
	}// updateDeprecatedScheduleStates()

	private static String getDeprecatedLimit() {
		Calendar calendar = Calendar.getInstance();
		calendar.add(Calendar.DAY_OF_YEAR, -DEPRECATED_LIMIT_IN_DAYS);
		Date limit = calendar.getTime();
		return Database.dateFormatter.format(limit);
	}// getDeprecatedLimit()

	protected void launcheUpdatesIfNecessary() {
		SharedPreferences preferences = this
				.getSharedPreferences(SERVICE_PREFS_NAME, Context.MODE_PRIVATE);
		if (preferences.contains(LAUNCH_AUTO_UPDATES)) {
			if (this.launchAutoUpdate()) {
				preferences.edit().remove(LAUNCH_AUTO_UPDATES).commit();
				this.startAutoUpdates();
			}// if
		}// if
	}// if

	/**
	 * 
	 * @return si les mises à jour ont pu êtres lancées
	 */
	protected boolean launchAutoUpdate() {
		// Pas de réseau, inutile de lancer les synchronisations automatiques
		if (!AdeWebOptions.networkIsAvailable(this)) {
			this.cancelAutoUpdates();
			SharedPreferences preferences = this
					.getSharedPreferences(SERVICE_PREFS_NAME,
							Context.MODE_PRIVATE);
			preferences.edit().putBoolean(LAUNCH_AUTO_UPDATES, true).commit();
			return false;
		}// if

		final Cursor cursor = this.getContentResolver()
				.query(AdeWebContract.Schedule.CONTENT_URI,
						AutoUpdateScheduleQuery.PROJECTION,
						null,
						null,
						AdeWebContract.Schedule.DEFAULT_SORT);
		while (cursor.moveToNext()) {
			long scheduleId = cursor.getLong(AutoUpdateScheduleQuery._ID);
			this.launchScheduleUpdate(scheduleId, true, true);
		}// while
		cursor.close();
		return true;
	}// launchAutoUpdate()

	interface DeprecatedScheduleQuery {
		// id pour le loader
		int _TOKEN = 0x1;

		// colonnes demandées au provider
		String[] PROJECTION = { BaseColumns._ID,
				AdeWebContract.Schedule.SCHEDULE_LAST_UPDATE };

		// colonnes des données demandés pour utiliser lors de la récupération
		// des données depuis le Cursor (appels à getString(int) p.ex.)
		int _ID = 0;
		int SCHEDULE_LAST_UPDATE = 1;

	}// interface DeprecatedScheduleQuery

	interface AutoUpdateScheduleQuery {
		// id pour le loader
		int _TOKEN = 0x1;

		// colonnes demandées au provider
		String[] PROJECTION = { BaseColumns._ID };

		// colonnes des données demandés pour utiliser lors de la récupération
		// des données depuis le Cursor (appels à getString(int) p.ex.)
		int _ID = 0;

	}// interface DeprecatedScheduleQuery

	interface ScheduleQuery {
		// id pour le loader
		int _TOKEN = 0x1;

		// colonnes demandées au provider
		String[] PROJECTION = { BaseColumns._ID,
				AdeWebContract.Schedule.SCHEDULE_NAME,
				AdeWebContract.Schedule.SCHEDULE_STATUS,
				AdeWebContract.Schedule.SCHEDULE_ALL_RESOURCES,
				AdeWebContract.Schedule.SCHEDULE_STUDENT_NUM,
				AdeWebContract.Schedule.SCHEDULE_PERIOD,
				AdeWebContract.Schedule.SCHEDULE_ADE_PROJECT_ID,
				AdeWebContract.Schedule.SCHEDULE_LAST_UPDATE };

		// colonnes des données demandés pour utiliser lors de la récupération
		// des données depuis le Cursor (appels à getString(int) p.ex.)
		int _ID = 0;
		int SCHEDULE_NAME = 1;
		int SCHEDULE_STATE = 2;
		int SCHEDULE_RESOURCES = 3;
		int SCHEDULE_STUDENT_NUM = 4;
		int SCHEDULE_PERIOD = 5;
		int SCHEDULE_ADE_PROJECT_ID = 6;
		int SCHEDULE_LAST_UPDATE = 7;

	}// interface ScheduleQuery

	interface LessonQuery {
		// id pour le loader
		int _TOKEN = 0x2;

		// colonnes demandées au provider
		String[] PROJECTION = { BaseColumns._ID,
				AdeWebContract.Lesson.LESSON_GUID,
				AdeWebContract.Lesson.LESSON_DATE,
				AdeWebContract.Lesson.LESSON_START_TIME,
				AdeWebContract.Lesson.LESSON_END_TIME,
				AdeWebContract.Lesson.LESSON_ROOMS,
				AdeWebContract.Lesson.LESSON_TEACHERS,
				AdeWebContract.Lesson.LESSON_TYPE,
				AdeWebContract.Lesson.LESSON_GROUPS,
				AdeWebContract.Lesson.LESSON_TITLE };

		// On ne sélectionne pas les cours supprimés
		String SELECTION = String.format("%s = %s", //$NON-NLS-1$
				AdeWebContract.Lesson.LESSON_DELETED,
				Integer.valueOf(Lesson.NOT_DELETED));

		// colonnes des données demandés pour utiliser lors de la récupération
		// des données depuis le Cursor (appels à getString(int) p.ex.)
		int _ID = 0;
		int LESSON_GUID = 1;
		int LESSON_DATE = 2;
		int LESSON_START_TIME = 3;
		int LESSON_END_TIME = 4;
		int LESSON_ROOMS = 5;
		int LESSON_TEACHERS = 6;
		int LESSON_TYPE = 7;
		int LESSON_GROUPS = 8;
		int LESSON_TITLE = 9;
	}// interface ScheduleQuery

}// class AdeWebService